/*
 * Copyright (c) 1997, 2003, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package com.sun.corba.se.impl.activation;

/**
 * @author Anita Jindal
 * @since JDK1.2
 */

import org.omg.CORBA.CompletionStatus;

import com.sun.corba.se.spi.activation.Server;
import com.sun.corba.se.spi.activation.EndPointInfo;
import com.sun.corba.se.spi.activation.ORBAlreadyRegistered;
import com.sun.corba.se.spi.activation.ORBPortInfo;
import com.sun.corba.se.spi.activation.InvalidORBid;
import com.sun.corba.se.spi.activation.ServerHeldDown;
import com.sun.corba.se.spi.activation.RepositoryPackage.ServerDef;
import com.sun.corba.se.spi.activation.IIOP_CLEAR_TEXT;
import com.sun.corba.se.spi.orb.ORB;

import com.sun.corba.se.impl.orbutil.ORBConstants;
import com.sun.corba.se.impl.logging.ActivationSystemException;

import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class ServerTableEntry {

  private final static int DE_ACTIVATED = 0;
  private final static int ACTIVATING = 1;
  private final static int ACTIVATED = 2;
  private final static int RUNNING = 3;
  private final static int HELD_DOWN = 4;


  private String printState() {
    String str = "UNKNOWN";

    switch (state) {
      case (DE_ACTIVATED):
        str = "DE_ACTIVATED";
        break;
      case (ACTIVATING):
        str = "ACTIVATING  ";
        break;
      case (ACTIVATED):
        str = "ACTIVATED   ";
        break;
      case (RUNNING):
        str = "RUNNING     ";
        break;
      case (HELD_DOWN):
        str = "HELD_DOWN   ";
        break;
      default:
        break;
    }

    return str;
  }

  private final static long waitTime = 2000;
  private static final int ActivationRetryMax = 5;

  // state of each entry
  private int state;
  private int serverId;
  private HashMap orbAndPortInfo;
  private Server serverObj;
  private ServerDef serverDef;
  private Process process;
  private int activateRetryCount = 0;
  private String activationCmd;
  private ActivationSystemException wrapper;

  public String toString() {
    return "ServerTableEntry[" + "state=" + printState() +
        " serverId=" + serverId +
        " activateRetryCount=" + activateRetryCount + "]";
  }

  // get the string needed to make the activation command
  private static String javaHome, classPath, fileSep, pathSep;

  static {
    javaHome = System.getProperty("java.home");
    classPath = System.getProperty("java.class.path");
    fileSep = System.getProperty("file.separator");
    pathSep = System.getProperty("path.separator");
  }

  ServerTableEntry(ActivationSystemException wrapper,
      int serverId, ServerDef serverDef, int initialPort,
      String dbDirName, boolean verify, boolean debug) {
    this.wrapper = wrapper;
    this.serverId = serverId;
    this.serverDef = serverDef;
    this.debug = debug;
    // create a HashMap with capacity 255
    // Since all methods are synchronized, we don't need any
    // additional synchronization mechanisms
    orbAndPortInfo = new HashMap(255);

    activateRetryCount = 0;
    state = ACTIVATING;

    // compute the activation command
    activationCmd =

        // add path to the java vm
        javaHome + fileSep + "bin" + fileSep + "java " +

            // add any arguments to the server Java VM
            serverDef.serverVmArgs + " " +

            // add ORB properties
            "-Dioser=" + System.getProperty("ioser") + " " +
            "-D" + ORBConstants.INITIAL_PORT_PROPERTY + "=" + initialPort + " " +
            "-D" + ORBConstants.DB_DIR_PROPERTY + "=" + dbDirName + " " +
            "-D" + ORBConstants.ACTIVATED_PROPERTY + "=true " +
            "-D" + ORBConstants.SERVER_ID_PROPERTY + "=" + serverId + " " +
            "-D" + ORBConstants.SERVER_NAME_PROPERTY + "=" + serverDef.serverName + " " +
            // we need to pass in the verify flag, so that the server is not
            // launched, when we try to validate its definition during registration
            // into the RepositoryImpl

            (verify ? "-D" + ORBConstants.SERVER_DEF_VERIFY_PROPERTY + "=true " : "") +

            // add classpath to the server
            "-classpath " + classPath +
            (serverDef.serverClassPath.equals("") == true ? "" : pathSep) +
            serverDef.serverClassPath +

            // add server class name and arguments
            " com.sun.corba.se.impl.activation.ServerMain " + serverDef.serverArgs

            // Add the debug flag, if any
            + (debug ? " -debug" : "");

    if (debug) {
      System.out.println(
          "ServerTableEntry constructed with activation command " +
              activationCmd);
    }
  }

  /**
   * Verify whether the server definition is valid.
   */
  public int verify() {
    try {

      if (debug) {
        System.out.println("Server being verified w/" + activationCmd);
      }

      process = Runtime.getRuntime().exec(activationCmd);
      int result = process.waitFor();
      if (debug) {
        printDebug("verify", "returns " + ServerMain.printResult(result));
      }
      return result;
    } catch (Exception e) {
      if (debug) {
        printDebug("verify", "returns unknown error because of exception " +
            e);
      }
      return ServerMain.UNKNOWN_ERROR;
    }
  }

  private void printDebug(String method, String msg) {
    System.out.println("ServerTableEntry: method  =" + method);
    System.out.println("ServerTableEntry: server  =" + serverId);
    System.out.println("ServerTableEntry: state   =" + printState());
    System.out.println("ServerTableEntry: message =" + msg);
    System.out.println();
  }

  synchronized void activate() throws org.omg.CORBA.SystemException {
    state = ACTIVATED;

    try {
      if (debug) {
        printDebug("activate", "activating server");
      }
      process = Runtime.getRuntime().exec(activationCmd);
    } catch (Exception e) {
      deActivate();
      if (debug) {
        printDebug("activate", "throwing premature process exit");
      }
      throw wrapper.unableToStartProcess();
    }
  }

  synchronized void register(Server server) {
    if (state == ACTIVATED) {

      serverObj = server;

      //state = RUNNING;
      //notifyAll();

      if (debug) {
        printDebug("register", "process registered back");
      }

    } else {

      if (debug) {
        printDebug("register", "throwing premature process exit");
      }
      throw wrapper.serverNotExpectedToRegister();
    }
  }

  synchronized void registerPorts(String orbId, EndPointInfo[] endpointList)
      throws ORBAlreadyRegistered {

    // find if the ORB is already registered, then throw an exception
    if (orbAndPortInfo.containsKey(orbId)) {
      throw new ORBAlreadyRegistered(orbId);
    }

    // store all listener ports and their types
    int numListenerPorts = endpointList.length;
    EndPointInfo[] serverListenerPorts = new EndPointInfo[numListenerPorts];

    for (int i = 0; i < numListenerPorts; i++) {
      serverListenerPorts[i] = new EndPointInfo(endpointList[i].endpointType, endpointList[i].port);
      if (debug) {
        System.out.println("registering type: " + serverListenerPorts[i].endpointType + "  port  "
            + serverListenerPorts[i].port);
      }
    }

    // put this set of listener ports in the HashMap associated
    // with the orbId
    orbAndPortInfo.put(orbId, serverListenerPorts);
    if (state == ACTIVATED) {
      state = RUNNING;
      notifyAll();
    }
    // _REVISIT_, If the state is not equal to ACTIVATED then it is a bug
    // need to log that error, once the Logging framework is in place
    // for rip-int.
    if (debug) {
      printDebug("registerPorts", "process registered Ports");
    }
  }

  void install() {
    Server localServerObj = null;
    synchronized (this) {
      if (state == RUNNING) {
        localServerObj = serverObj;
      } else {
        throw wrapper.serverNotRunning();
      }
    }
    if (localServerObj != null) {
      localServerObj.install();
    }

  }

  void uninstall() {
    Server localServerObj = null;
    Process localProcess = null;

    synchronized (this) {
      localServerObj = serverObj;
      localProcess = process;

      if (state == RUNNING) {

        deActivate();

      } else {
        throw wrapper.serverNotRunning();
      }
    }
    try {
      if (localServerObj != null) {
        localServerObj.shutdown(); // shutdown the server
        localServerObj.uninstall(); // call the uninstall
      }

      if (localProcess != null) {
        localProcess.destroy();
      }
    } catch (Exception ex) {
      // what kind of exception should be thrown
    }
  }

  synchronized void holdDown() {
    state = HELD_DOWN;

    if (debug) {
      printDebug("holdDown", "server held down");
    }

    notifyAll();
  }

  synchronized void deActivate() {
    state = DE_ACTIVATED;

    if (debug) {
      printDebug("deActivate", "server deactivated");
    }

    notifyAll();
  }

  synchronized void checkProcessHealth() {
    // If the State in the ServerTableEntry is RUNNING and the
    // Process was shut down abnormally, The method will change the
    // server state as De-Activated.
    if (state == RUNNING) {
      try {
        int exitVal = process.exitValue();
      } catch (IllegalThreadStateException e1) {
        return;
      }
      synchronized (this) {
        // Clear the PortInformation as it is old
        orbAndPortInfo.clear();
        // Move the state to De-Activated, So that the next
        // call to this server will re-activate.
        deActivate();
      }
    }
  }

  synchronized boolean isValid() {
    if ((state == ACTIVATING) || (state == HELD_DOWN)) {
      if (debug) {
        printDebug("isValid", "returns true");
      }

      return true;
    }

    try {
      int exitVal = process.exitValue();
    } catch (IllegalThreadStateException e1) {
      return true;
    }

    if (state == ACTIVATED) {
      if (activateRetryCount < ActivationRetryMax) {
        if (debug) {
          printDebug("isValid", "reactivating server");
        }
        activateRetryCount++;
        activate();
        return true;
      }

      if (debug) {
        printDebug("isValid", "holding server down");
      }

      holdDown();
      return true;
    }

    deActivate();
    return false;
  }

  synchronized ORBPortInfo[] lookup(String endpointType) throws ServerHeldDown {
    while ((state == ACTIVATING) || (state == ACTIVATED)) {
      try {
        wait(waitTime);
        if (!isValid()) {
          break;
        }
      } catch (Exception e) {
      }
    }
    ORBPortInfo[] orbAndPortList = null;

    if (state == RUNNING) {
      orbAndPortList = new ORBPortInfo[orbAndPortInfo.size()];
      Iterator setORBids = orbAndPortInfo.keySet().iterator();

      try {
        int numElements = 0;
        int i;
        int port;
        while (setORBids.hasNext()) {
          String orbId = (String) setORBids.next();
          // get an entry corresponding to orbId
          EndPointInfo[] serverListenerPorts = (EndPointInfo[]) orbAndPortInfo.get(orbId);
          port = -1;
          // return the port corresponding to the endpointType
          for (i = 0; i < serverListenerPorts.length; i++) {
            if (debug) {
              System.out.println("lookup num-ports " + serverListenerPorts.length + "   " +
                  serverListenerPorts[i].endpointType + "   " +
                  serverListenerPorts[i].port);
            }
            if ((serverListenerPorts[i].endpointType).equals(endpointType)) {
              port = serverListenerPorts[i].port;
              break;
            }
          }
          orbAndPortList[numElements] = new ORBPortInfo(orbId, port);
          numElements++;
        }
      } catch (NoSuchElementException e) {
        // have everything in the table
      }
      return orbAndPortList;
    }

    if (debug) {
      printDebug("lookup", "throwing server held down error");
    }

    throw new ServerHeldDown(serverId);
  }

  synchronized EndPointInfo[] lookupForORB(String orbId)
      throws ServerHeldDown, InvalidORBid {
    while ((state == ACTIVATING) || (state == ACTIVATED)) {
      try {
        wait(waitTime);
        if (!isValid()) {
          break;
        }
      } catch (Exception e) {
      }
    }
    EndPointInfo[] portList = null;

    if (state == RUNNING) {

      try {

        // get an entry corresponding to orbId
        EndPointInfo[] serverListenerPorts = (EndPointInfo[]) orbAndPortInfo.get(orbId);

        portList = new EndPointInfo[serverListenerPorts.length];
        // return the port corresponding to the endpointType
        for (int i = 0; i < serverListenerPorts.length; i++) {
          if (debug) {
            System.out.println("lookup num-ports " + serverListenerPorts.length + "   "
                + serverListenerPorts[i].endpointType + "   " +
                serverListenerPorts[i].port);
          }
          portList[i] = new EndPointInfo(serverListenerPorts[i].endpointType,
              serverListenerPorts[i].port);
        }
      } catch (NoSuchElementException e) {
        // no element in HashMap corresponding to ORBid found
        throw new InvalidORBid();
      }
      return portList;
    }

    if (debug) {
      printDebug("lookup", "throwing server held down error");
    }

    throw new ServerHeldDown(serverId);
  }

  synchronized String[] getORBList() {
    String[] orbList = new String[orbAndPortInfo.size()];
    Iterator setORBids = orbAndPortInfo.keySet().iterator();

    try {
      int numElements = 0;
      while (setORBids.hasNext()) {
        String orbId = (String) setORBids.next();
        orbList[numElements++] = orbId;
      }
    } catch (NoSuchElementException e) {
      // have everything in the table
    }
    return orbList;
  }

  int getServerId() {
    return serverId;
  }

  boolean isActive() {
    return (state == RUNNING) || (state == ACTIVATED);
  }

  synchronized void destroy() {

    Server localServerObj = null;
    Process localProcess = null;

    synchronized (this) {
      localServerObj = serverObj;
      localProcess = process;

      deActivate();
    }

    try {
      if (localServerObj != null) {
        localServerObj.shutdown();
      }

      if (debug) {
        printDebug("destroy", "server shutdown successfully");
      }
    } catch (Exception ex) {
      if (debug) {
        printDebug("destroy",
            "server shutdown threw exception" + ex);
      }
      // ex.printStackTrace();
    }

    try {
      if (localProcess != null) {
        localProcess.destroy();
      }

      if (debug) {
        printDebug("destroy", "process destroyed successfully");
      }
    } catch (Exception ex) {
      if (debug) {
        printDebug("destroy",
            "process destroy threw exception" + ex);
      }

      // ex.printStackTrace();
    }
  }

  private boolean debug = false;
}
